"""
投掷的硬币
"""
import random
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Manager
import time
from meta import LogMixin, MAX_PROCESS_NUM, TimerMixin


class DefaultCoin():
    """硬币类

    高维度硬币，可以指定硬币面数。
    属性：
        self.num: 硬币面数

    """

    def __init__(self, num=2):
        super().__init__()
        if num < 2:
            raise ValueError("硬币面数不能小于2")
        self.num = num

    def __str__(self):
        return f"{self.num}面硬币"

    def flip(self):
        """扔硬币"""
        return random.randint(1, self.num)


class Coin(DefaultCoin, LogMixin):
    """日志硬币"""
    def __init__(self, num=2):
        super().__init__(num)
        self.logger.info(f'{self.__str__()}初始化完毕')

    def flip(self):
        result = super().flip()
        return result

    def flips(self, max_flips: int):
        """迭代扔硬币"""
        now = 0
        while now < max_flips:
            yield self.flip()
            now += 1
 
class MemoryCoin(Coin, TimerMixin):
    """有记忆的硬币"""
    def __init__(self, num=2):
        """记忆硬币初始化
        
        :params:num不得大于unsigned int的最大值一般不大于4294967295
        :property:possiblity: 等可能性
        :property:weight: 各个等可能性的权重
        
        """
        super().__init__(num)

        self.possibility = [i for i in range(1, self.num + 1)]
        self.weight = Manager().Array('I', [1 for _ in range(self.num)])  # 权重不得为0，使用Manager进行多进程同步
        self.n = Manager().Array('I', [0])  # 当前抛出次数
        self.logger.debug(f'{self.possibility}')
        

        self.logger.info(f'记忆硬币{self.__str__()}初始化完毕')

    def flip(self, num: int=None):
        """扔硬币
        
        返回一次或多次扔硬币结果
        
        """
        def oneshot(): 
            result = random.choices(population=self.possibility, weights=self.weight)[0]
            for i in range(0, len(self.weight)):
                self.weight[i] += 1
            self.weight[result - 1] -= 1
            return result
        if num is None:
            return oneshot()
        else:
            return [oneshot() for _ in range(num)]
            
    def flips(self, num):
        """多进程扔硬币生成器"""
        _chunk_size = num // MAX_PROCESS_NUM
        _left_size = num % MAX_PROCESS_NUM

        # 不需要多进程
        if _chunk_size == 0:
            yield map(self.flip, range(_left_size))

        # 需要多进程
        else:
            with ProcessPoolExecutor(max_workers=MAX_PROCESS_NUM) as executor:
                yield from executor.map(self.flip, [None for _ in range(num)], chunksize=_chunk_size)